/*
  TeX Live user docs
  - source: ../../../../../doc/languages-frameworks/texlive.xml
  - current html: https://nixos.org/nixpkgs/manual/#sec-language-texlive
*/
{
  lib,
  stdenv,
  fetchpatch,
  fetchurl,
  runCommand,
  writeShellScript,
  writeText,
  buildEnv,
  callPackage,
  ghostscript_headless,
  harfbuzz,
  makeWrapper,
  installShellFiles,
  python3,
  ruby,
  perl,
  tk,
  jdk,
  bash,
  snobol4,
  coreutils,
  findutils,
  gawk,
  getopt,
  gnugrep,
  gnumake,
  gnupg,
  gnused,
  gzip,
  html-tidy,
  ncurses,
  zip,
  libfaketime,
  asymptote,
  biber-ms,
  makeFontsConf,
  useFixedHashes ? true,
  extraMirrors ? [ ],
  nixfmt,
  luajit,
}:
let
  # various binaries (compiled)
  bin = callPackage ./bin.nix {
    ghostscript = ghostscript_headless;
    harfbuzz = harfbuzz.override {
      withIcu = true;
      withGraphite2 = true;
    };
    inherit useFixedHashes;
    tlpdb = overriddenTlpdb;
  };

  tlpdb = import ./tlpdb.nix;

  tlpdbVersion = tlpdb."00texlive.config";

  # the set of TeX Live packages, collections, and schemes; using upstream naming
  overriddenTlpdb =
    let
      overrides = import ./tlpdb-overrides.nix {
        inherit
          stdenv
          lib
          fetchpatch
          bin
          tlpdb
          tlpdbxz
          tl
          installShellFiles
          coreutils
          findutils
          gawk
          getopt
          ghostscript_headless
          gnugrep
          gnumake
          gnupg
          gnused
          gzip
          html-tidy
          ncurses
          perl
          python3
          ruby
          zip
          luajit
          ;
      };
    in
    overrides tlpdb;

  version = {
    # day of the snapshot being taken
    year = "2025";
    month = "07";
    day = "03";
    # TeX Live version
    texliveYear = 2025;
    # final (historic) release or snapshot
    final = false;
  };

  # The tarballs on CTAN mirrors for the current release are constantly
  # receiving updates, so we can't use those directly. Stable snapshots
  # need to be used instead. Ideally, for the release branches of NixOS we
  # should be switching to the tlnet-final versions
  # (https://tug.org/historic/).
  mirrors =
    extraMirrors
    ++ (
      if version.final then
        [
          # tlnet-final snapshot; used when texlive.tlpdb is frozen
          # the TeX Live yearly freeze typically happens in mid-March
          "http://ftp.math.utah.edu/pub/tex/historic/systems/texlive/${toString version.texliveYear}/tlnet-final"
          "ftp://tug.org/texlive/historic/${toString version.texliveYear}/tlnet-final"
        ]
      else
        [
          # CTAN mirrors
          "https://mirror.ctan.org/systems/texlive/tlnet"
          # daily snapshots hosted by one of the texlive release managers;
          # used for packages that in the meanwhile have been updated or removed from CTAN
          # and for packages that have not reached yet the historic mirrors
          # please note that this server is not meant for large scale deployment
          # https://tug.org/pipermail/tex-live/2019-November/044456.html
          # https://texlive.info/ MUST appear last (see tlpdbxz)
          "https://texlive.info/tlnet-archive/${version.year}/${version.month}/${version.day}/tlnet"
        ]
    );

  tlpdbxz = fetchurl {
    urls =
      map (up: "${up}/tlpkg/texlive.tlpdb.xz")
        # use last mirror for daily snapshots as texlive.tlpdb.xz changes every day
        # TODO make this less hacky
        (if version.final then mirrors else [ (lib.last mirrors) ]);
    hash = "sha256-hTWTs5meP6X7+bBGEHP9pDv8eJTfvBZFKX0WeK8+aZg=";
  };

  tlpdbNix =
    runCommand "tlpdb.nix"
      {
        inherit tlpdbxz;
        tl2nix = ./tl2nix.sed;
      }
      ''
        xzcat "$tlpdbxz" | sed -rn -f "$tl2nix" | uniq | ${lib.getExe nixfmt} > "$out"
      '';

  # map: name -> fixed-output hash
  fixedHashes = lib.optionalAttrs useFixedHashes (import ./fixed-hashes.nix);

  buildTeXLivePackage = import ./build-texlive-package.nix {
    inherit
      lib
      fetchurl
      runCommand
      writeShellScript
      bash
      jdk
      perl
      python3
      ruby
      snobol4
      tk
      ;
    texliveBinaries = bin;
  };

  tl = lib.mapAttrs (
    pname:
    {
      revision,
      extraRevision ? "",
      ...
    }@args:
    buildTeXLivePackage (
      args
      # NOTE: the fixed naming scheme must match generate-fixed-hashes.nix
      // {
        inherit mirrors pname;
        fixedHashes = fixedHashes."${pname}-${toString revision}${extraRevision}" or { };
      }
      // lib.optionalAttrs (args ? deps) { deps = map (n: tl.${n}) (args.deps or [ ]); }
    )
  ) overriddenTlpdb;

  # function for creating a working environment
  buildTeXEnv = import ./build-tex-env.nix {
    inherit bin tl;
    ghostscript = ghostscript_headless;
    inherit
      lib
      buildEnv
      libfaketime
      makeFontsConf
      makeWrapper
      runCommand
      writeShellScript
      writeText
      toTLPkgSets
      bash
      perl
      coreutils
      gawk
      gnugrep
      gnused
      ;
  };

  ### texlive.combine compatibility layer:
  # convert TeX packages to { pkgs = [ ... ]; } lists
  # respecting specified outputs
  toTLPkgList =
    drv:
    if drv.outputSpecified or false then
      let
        tlType = drv.tlType or tlOutToType.${drv.tlOutputName or drv.outputName} or null;
      in
      lib.optional (tlType != null) (drv // { inherit tlType; })
    else
      [ (drv.tex // { tlType = "run"; }) ]
      ++ lib.optional (drv ? texdoc) (
        drv.texdoc // { tlType = "doc"; } // lib.optionalAttrs (drv ? man) { hasManpages = true; }
      )
      ++ lib.optional (drv ? texsource) (drv.texsource // { tlType = "source"; })
      ++ lib.optional (drv ? tlpkg) (drv.tlpkg // { tlType = "tlpkg"; })
      ++ lib.optional (drv ? out) (drv.out // { tlType = "bin"; });
  tlOutToType = {
    out = "bin";
    tex = "run";
    texsource = "source";
    texdoc = "doc";
    tlpkg = "tlpkg";
  };

  # convert { pkgs = [ ... ]; } lists to TeX packages
  # possibly more than one, if pkgs is also used to specify dependencies
  tlTypeToOut = {
    run = "tex";
    doc = "texdoc";
    source = "texsource";
    bin = "out";
    tlpkg = "tlpkg";
  };
  toSpecifiedNV = p: rec {
    name = value.tlOutputName;
    value = removeAttrs p [ "pkgs" ] // {
      outputSpecified = true;
      tlOutputName = tlTypeToOut.${p.tlType};
    };
  };
  toTLPkgSet =
    pname: drvs:
    let
      set = lib.listToAttrs (map toSpecifiedNV drvs);
      mainDrv = set.out or set.tex or set.tlpkg or set.texdoc or set.texsource;
    in
    removeAttrs mainDrv [ "outputSpecified" ];
  toTLPkgSets = { pkgs, ... }: lib.mapAttrsToList toTLPkgSet (lib.groupBy (p: p.pname) pkgs);

  # export TeX packages as { pkgs = [ ... ]; } in the top attribute set
  allPkgLists = lib.mapAttrs (n: drv: { pkgs = toTLPkgList drv; }) tl;

  # function for creating a working environment from a set of TL packages
  # now a legacy wrapper around buildTeXEnv
  combine = import ./combine-wrapper.nix {
    inherit
      buildTeXEnv
      lib
      toTLPkgList
      toTLPkgSets
      ;
  };

  assertions =
    lib.assertMsg (
      tlpdbVersion.year == version.texliveYear
    ) "TeX Live year in texlive does not match tlpdb.nix, refusing to evaluate"
    && lib.assertMsg (
      tlpdbVersion.frozen == version.final
    ) "TeX Live final status in texlive does not match tlpdb.nix, refusing to evaluate";

  # Pre-defined environment packages for TeX Live schemes,
  # to make nix-env usage more comfortable and build selected on Hydra.

  # these license lists should be the sorted union of the licenses of the packages the schemes contain.
  # The correctness of this collation is tested by tests.texlive.licenses
  licenses = with lib.licenses; {
    scheme-basic = [
      cc-by-sa-40
      free
      gfl
      gpl1Only
      gpl2Only
      gpl2Plus
      knuth
      lgpl21
      lppl1
      lppl13c
      mit
      ofl
      publicDomain
    ];
    scheme-bookpub = [
      artistic2
      asl20
      bsd3
      cc-by-sa-40
      fdl13Only
      free
      gfl
      gpl1Only
      gpl2Only
      gpl2Plus
      knuth
      lgpl21
      lppl1
      lppl12
      lppl13c
      mit
      ofl
      publicDomain
    ];
    scheme-context = [
      bsd2
      bsd3
      cc-by-sa-40
      eupl12
      fdl13Only
      free
      gfl
      gfsl
      gpl1Only
      gpl2Only
      gpl2Plus
      gpl3Only
      gpl3Plus
      knuth
      lgpl2
      lgpl21
      lppl1
      lppl13c
      mit
      ofl
      publicDomain
      x11
    ];
    scheme-full = [
      artistic1-cl8
      artistic2
      asl20
      bsd2
      bsd3
      bsdOriginal
      cc-by-10
      cc-by-20
      cc-by-30
      cc-by-40
      cc-by-sa-10
      cc-by-sa-20
      cc-by-sa-30
      cc-by-sa-40
      cc0
      eupl12
      fdl13Only
      free
      gfl
      gfsl
      gpl1Only
      gpl2Only
      gpl2Plus
      gpl3Only
      gpl3Plus
      isc
      knuth
      lgpl2
      lgpl21
      lgpl3
      lppl1
      lppl12
      lppl13a
      lppl13c
      mit
      ofl
      publicDomain
      x11
    ];
    scheme-gust = [
      artistic1-cl8
      asl20
      bsd2
      bsd3
      cc-by-40
      cc-by-sa-40
      cc0
      eupl12
      fdl13Only
      free
      gfl
      gfsl
      gpl1Only
      gpl2Only
      gpl2Plus
      gpl3Only
      gpl3Plus
      knuth
      lgpl2
      lgpl21
      lppl1
      lppl12
      lppl13c
      mit
      ofl
      publicDomain
      x11
    ];
    scheme-infraonly = [
      gpl2Plus
      lgpl21
    ];
    scheme-medium = [
      artistic1-cl8
      asl20
      bsd2
      bsd3
      cc-by-40
      cc-by-sa-20
      cc-by-sa-30
      cc-by-sa-40
      cc0
      eupl12
      fdl13Only
      free
      gfl
      gpl1Only
      gpl2Only
      gpl2Plus
      gpl3Only
      gpl3Plus
      isc
      knuth
      lgpl2
      lgpl21
      lgpl3
      lppl1
      lppl12
      lppl13a
      lppl13c
      mit
      ofl
      publicDomain
      x11
    ];
    scheme-minimal = [
      cc-by-sa-40
      free
      gpl1Only
      gpl2Plus
      knuth
      lgpl21
      lppl1
      lppl13c
      mit
      ofl
      publicDomain
    ];
    scheme-small = [
      asl20
      cc-by-40
      cc-by-sa-40
      cc0
      eupl12
      fdl13Only
      free
      gfl
      gpl1Only
      gpl2Only
      gpl2Plus
      gpl3Only
      gpl3Plus
      knuth
      lgpl2
      lgpl21
      lppl1
      lppl12
      lppl13c
      mit
      ofl
      publicDomain
      x11
    ];
    scheme-tetex = [
      artistic1-cl8
      asl20
      bsd2
      bsd3
      cc-by-30
      cc-by-40
      cc-by-sa-10
      cc-by-sa-20
      cc-by-sa-30
      cc-by-sa-40
      cc0
      eupl12
      fdl13Only
      free
      gfl
      gpl1Only
      gpl2Only
      gpl2Plus
      gpl3Only
      gpl3Plus
      isc
      knuth
      lgpl2
      lgpl21
      lgpl3
      lppl1
      lppl12
      lppl13a
      lppl13c
      mit
      ofl
      publicDomain
      x11
    ];
  };

  meta = {
    description = "TeX Live environment";
    platforms = lib.platforms.all;
    maintainers = with lib.maintainers; [ veprbl ];
    license = licenses.scheme-infraonly;
  };

  combined = lib.recurseIntoAttrs (
    lib.genAttrs
      [
        "scheme-basic"
        "scheme-bookpub"
        "scheme-context"
        "scheme-full"
        "scheme-gust"
        "scheme-infraonly"
        "scheme-medium"
        "scheme-minimal"
        "scheme-small"
        "scheme-tetex"
      ]
      (
        pname:
        (buildTeXEnv {
          __extraName = "combined" + lib.removePrefix "scheme" pname;
          __extraVersion =
            if version.final then "-final" else ".${version.year}${version.month}${version.day}";
          requiredTeXPackages = ps: [ ps.${pname} ];
          # to maintain full backward compatibility, enable texlive.combine behavior
          __combine = true;
        }).overrideAttrs
          {
            meta = meta // {
              description = "TeX Live environment for ${pname}";
              license = licenses.${pname};
            };
          }
      )
  );

  schemes = lib.listToAttrs (
    map
      (s: {
        name = "texlive" + s;
        value = lib.addMetaAttrs { license = licenses.${"scheme-" + (lib.toLower s)}; } (buildTeXEnv {
          requiredTeXPackages = ps: [ ps.${"scheme-" + (lib.toLower s)} ];
        });
      })
      [
        "Basic"
        "BookPub"
        "ConTeXt"
        "Full"
        "GUST"
        "InfraOnly"
        "Medium"
        "Minimal"
        "Small"
        "TeTeX"
      ]
  );

in
allPkgLists
// {
  pkgs = tl;

  tlpdb = {
    # nested in an attribute set to prevent them from appearing in search
    nix = tlpdbNix;
    xz = tlpdbxz;
  };

  bin =
    assert assertions;
    bin
    // {
      # for backward compatibility
      latexindent = tl.latexindent;
    };

  combine =
    assert assertions;
    combine;

  combined =
    assert assertions;
    combined;

  inherit schemes;

  # convenience alias
  withPackages = (buildTeXEnv { }).withPackages;
}
